//-----------------------------------------------------------------------
// <copyright file="VersionNumber.cs" company="Rhys Parry">
//    Copyright Rhys Parry 2009
//    This file is released under the Apache 2.0 License
// </copyright>
//-----------------------------------------------------------------------
namespace Smarts.Core
{
    using System;
    using System.Globalization;
    using System.Linq;
    using RegularExpressions;

    /// <summary>
    /// Represents a software version number, broken into four parts, major, minor, revision and build.
    /// </summary>
    public class VersionNumber : IComparable<VersionNumber>, IEquatable<VersionNumber>, IFormattable
    {
        /// <summary>
        /// Backing field for the major version number.
        /// </summary>
        private readonly int _major;

        /// <summary>
        /// Backing field for the minor version number.
        /// </summary>
        private readonly int _minor;
        
        /// <summary>
        /// Backing field for the revision version number.
        /// </summary>
        private readonly int _revision;

        /// <summary>
        /// Backing field for the build version number.
        /// </summary>
        private readonly int _build;

        /// <summary>
        /// Initializes a new instance of the <see cref="VersionNumber"/> class.
        /// </summary>
        /// <param name="major">The major version number.</param>
        public VersionNumber(int major)
            : this(major, 0, 0, 0)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="VersionNumber"/> class.
        /// </summary>
        /// <param name="major">The major version number.</param>
        /// <param name="minor">The minor version number.</param>
        public VersionNumber(int major, int minor)
            : this(major, minor, 0, 0)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="VersionNumber"/> class.
        /// </summary>
        /// <param name="major">The major version number.</param>
        /// <param name="minor">The minor version number.</param>
        /// <param name="revision">The revision version number.</param>
        public VersionNumber(int major, int minor, int revision)
            : this(major, minor, revision, 0)
        {   
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="VersionNumber"/> class.
        /// </summary>
        /// <param name="major">The major version number.</param>
        /// <param name="minor">The minor version number.</param>
        /// <param name="revision">The revision version number.</param>
        /// <param name="build">The build version number.</param>
        public VersionNumber(int major, int minor, int revision, int build)
        {
            _major = major;
            _minor = minor;
            _revision = revision;
            _build = build;
        }

        /// <summary>
        /// Gets the major version number.
        /// </summary>
        /// <value>The major version number.</value>
        public int Major
        {
            get
            {
                return _major;
            }
        }

        /// <summary>
        /// Gets the minor version number.
        /// </summary>
        /// <value>The minor version number.</value>
        public int Minor
        {
            get
            {
                return _minor;
            }
        }

        /// <summary>
        /// Gets the revision number.
        /// </summary>
        /// <value>The revision number.</value>
        public int Revision
        {
            get
            {
                return _revision;
            }
        }

        /// <summary>
        /// Gets the build number.
        /// </summary>
        /// <value>The build number.</value>
        public int Build
        {
            get
            {
                return _build;
            }
        }

        /// <summary>
        /// Parses the specified version number.
        /// </summary>
        /// <param name="versionNumber">The version number.</param>
        /// <returns>The version number identified from the <paramref name="versionNumber"/> parameter.</returns>
        public static VersionNumber Parse(string versionNumber)
        {
            var versions = versionNumber.Split(new[] { '.' }).Select(v => int.Parse(v)).ToArray();

            if (versions.Length == 4)
            {
                return new VersionNumber(versions[0], versions[1], versions[2], versions[3]);
            }
            
            if (versions.Length == 3)
            {
                return new VersionNumber(versions[0], versions[1], versions[2]);
            }

            if (versions.Length == 2)
            {
                return new VersionNumber(versions[0], versions[1]);
            }

            if (versions.Length == 1)
            {
                return new VersionNumber(versions[0]);
            }

            throw new FormatException("Input string was not in a correct format.");
        }

        /// <summary>
        /// Tries to parse a version number.
        /// </summary>
        /// <param name="versionNumber">The version number.</param>
        /// <param name="result">The parsed output.</param>
        /// <returns><c>true</c> if the version number could be parsed; otherwise, <c>false</c>.</returns>
        public static bool TryParse(string versionNumber, out VersionNumber result)
        {
            result = null;
            if (!Regex.IsMatch(versionNumber, @"^(([0-9]|[1-9][0-9])+\.){0,3}([0-9]|[1-9][0-9]+)$"))
            {
                return false;
            }

            result = Parse(versionNumber);
            return true;
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
        /// Value
        /// Meaning
        /// Less than zero
        /// This object is less than the <paramref name="other"/> parameter.
        /// Zero
        /// This object is equal to <paramref name="other"/>.
        /// Greater than zero
        /// This object is greater than <paramref name="other"/>.
        /// </returns>
        public int CompareTo(VersionNumber other)
        {
            var majorCompare = Major.CompareTo(other.Major);
            if (majorCompare != 0)
            {
                return majorCompare;
            }

            var minorCompare = Minor.CompareTo(other.Minor);
            if (minorCompare != 0)
            {
                return minorCompare;
            }

            var revisionCompare = Revision.CompareTo(other.Revision);
            if (revisionCompare != 0)
            {
                return minorCompare;
            }

            return Build.CompareTo(other.Build);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        public bool Equals(VersionNumber other)
        {
            return CompareTo(other) == 0;
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return ToString("G", CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <param name="formatProvider">The format provider.</param>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            return string.Format(formatProvider, "{0}.{1}.{2}.{3}", Major, Minor, Revision, Build);
        }
    }
}